package unittest;

import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.StringDef;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author ：zhong.jw
 * @date ：Created in 2022/7/11 9:30
 * <p>
 * 描述：根据bean类型【包含对象、接口、数组...】，创建bean
 */
public final class BeanHelper {

    private static final int MAX_STACK_DEEP = 2000;

    private static final Set<String> sBasicClass = new HashSet<>(16, 1f);
    private static final String JAVA_LANG_INTEGER = "java.lang.Integer";
    private static final String JAVA_LANG_BYTE = "java.lang.Byte";
    private static final String JAVA_LANG_SHORT = "java.lang.Short";
    private static final String JAVA_LANG_LONG = "java.lang.Long";
    private static final String JAVA_LANG_FLOAT = "java.lang.Float";
    private static final String JAVA_LANG_DOUBLE = "java.lang.Double";
    private static final String JAVA_LANG_BOOLEAN = "java.lang.Boolean";
    private static final String JAVA_LANG_STRING = "java.lang.String";
    private static final String JAVA_LANG_CHARACTER = "java.lang.Character";
    private static final String JAVA_INTEGER = "int";
    private static final String JAVA_BYTE = "byte";
    private static final String JAVA_SHORT = "short";
    private static final String JAVA_LONG = "long";
    private static final String JAVA_FLOAT = "float";
    private static final String JAVA_DOUBLE = "double";
    private static final String JAVA_BOOLEAN = "boolean";
    private static final String JAVA_CHARACTER = "character";

    private static final String NUMBER_DEFAULT = "0";
    private static final String BOOLEAN_DEFAULT = "true";

    public static final String APP_PRO_NAME = "com.incall";
    private static final Map<Class<?>, Object> mCacheObj = new HashMap<>();

    private static int stackCount = 0;

    static {
        sBasicClass.add(JAVA_LANG_INTEGER);
        sBasicClass.add(JAVA_LANG_STRING);
        sBasicClass.add(JAVA_LANG_SHORT);
        sBasicClass.add(JAVA_LANG_BYTE);
        sBasicClass.add(JAVA_LANG_LONG);
        sBasicClass.add(JAVA_LANG_DOUBLE);
        sBasicClass.add(JAVA_LANG_CHARACTER);
        sBasicClass.add(JAVA_LANG_BOOLEAN);
        sBasicClass.add(JAVA_INTEGER);
        sBasicClass.add(JAVA_SHORT);
        sBasicClass.add(JAVA_BYTE);
        sBasicClass.add(JAVA_LONG);
        sBasicClass.add(JAVA_FLOAT);
        sBasicClass.add(JAVA_DOUBLE);
        sBasicClass.add(JAVA_BOOLEAN);
        sBasicClass.add(JAVA_CHARACTER);
    }

    private BeanHelper() {
        throw new UnsupportedOperationException("try to instance utils.");
    }

    public static <T> T bean(Class<T> clazz) {
        stackCount = 0;
        return performInstance(clazz, false);
    }

    public static <T> T beanAllField(Class<T> clazz) {
        return performInstance(clazz, true);
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> allConstance(Class<?> clazz, Class<T> type) {
        Field[] fields = clazz.getFields();
        List<T> result = new ArrayList<>();

        for (Field field : fields) {
            int modifiers = field.getModifiers();
            if ((modifiers & Modifier.FINAL) != 0
                    || (modifiers & Modifier.STATIC) != 0
                    || field.getType().equals(type)) {
                try {
                    result.add((T) field.get(null));
                } catch (IllegalAccessException e) {
                    Log.e("allConstance", "allConstance: ");
                }
            }
        }
        return result;
    }

    /**
     * 依赖默认构造方法
     */
    @SuppressWarnings("all")
    private static <T> T performInstance(Class<T> clazz, boolean instanceAll) {
        T t = validInstance(clazz);

        if (instanceAll) {
            Field[] fields = t.getClass().getDeclaredFields();
            for (Field field : fields) {
                field.setAccessible(true);
                try {
                    Object o = field.get(t);
                    if (o == null) {
                        field.set(t, performInstance(field.getType(), true));
                    }
                } catch (Exception e) {
                    ignore(e);
                }
            }
        }
        return t;
    }

    @SuppressWarnings("unchecked")
    private static <T> T validInstance(Class<T> clazz) {
        if (stackCount == MAX_STACK_DEEP) {
            return null;
        }
        stackCount++;
        T t = validSpecialInstanceIfNeed(clazz);
        if (t != null) {
            return t;
        }

        Constructor<?>[] constructors = clazz.getDeclaredConstructors();
        if (constructors.length == 0) {
            throw new IllegalArgumentException("can't invoke instance for " + clazz + " ,no declared constructor");
        }

        Arrays.sort(constructors, (o1, o2) -> {
            //构造方法排序依据，
            //无参构造方法最前
            //其次是基本类型靠前
            //最后根据非基本类型构造方法数作比较
            if (o1.getParameterCount() == 0) {
                return -1;
            }
            if (o2.getParameterCount() == 0) {
                return 1;
            }

            Class<?>[] parameterTypes1 = o1.getParameterTypes();
            Class<?>[] parameterTypes2 = o2.getParameterTypes();
            long count1 = Arrays.stream(parameterTypes1).filter(BeanHelper::isBasicType).count();
            long count2 = Arrays.stream(parameterTypes2).filter(BeanHelper::isBasicType).count();

            if (count1 == 0 && count2 == 0) {
                return o1.getParameterCount() - o2.getParameterCount();
            }

            return (int) (count2 - count1);
        });

        for (Constructor<?> constructor : constructors) {
            constructor.setAccessible(true);
            Object[] params = genericEmptyConstructorParams(constructor);
            try {
                t = (T) constructor.newInstance(params);
                break;
            } catch (Exception e) {
                ignore(e);
            }
        }

        if (t == null) {
            throw new RuntimeException("can't invoke instance for " + clazz);
        }

        //检查field
        //validFieldIfNeed(t);
        mCacheObj.put(t.getClass(), t);
        stackCount--;
        return t;
    }

    private static <T> void validFieldIfNeed(T t) {
        Field[] fields = t.getClass().getDeclaredFields();
        for (Field field : fields) {
            try {
                field.setAccessible(true);
                Object obj = field.get(t);
                if (obj == null) {
                    obj = validInstance(field.getType());
                    field.set(t, obj);
                }
            } catch (Exception ignore) {

            }
        }
    }

    @SuppressWarnings("unchecked")
    private static <T> T validSpecialInstanceIfNeed(Class<T> clazz) {
        if (clazz.equals(List.class)) {
            return (T) new ArrayList<>(0);
        }
        if (clazz.equals(Map.class)) {
            return (T) new HashMap<>();
        }
        if (clazz.equals(ArrayList.class)) {
            return (T) new ArrayList<>(0);
        }
        if (isArray(clazz)) {
            return (T) forArray(clazz);
        }
        if (isBasicType(clazz)) {
            if (clazz.getName().equals(JAVA_LANG_BOOLEAN)
                    || clazz.getName().equals(JAVA_BOOLEAN)) {
                return (T) basicValueOf(BOOLEAN_DEFAULT, clazz);
            } else {
                return (T) basicValueOf(NUMBER_DEFAULT, clazz);
            }
        }

        if (clazz.isInterface()) {
            //对于接口类型的传入，直接生成动态代理对象即可
            return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, (proxy, method, args) -> {
                Class<?> returnType = method.getReturnType();
                if (isBasicType(returnType)) {
                    if (returnType.getName().equals(JAVA_LANG_BOOLEAN)) {
                        return (T) basicValueOf(BOOLEAN_DEFAULT, returnType);
                    } else {
                        return (T) basicValueOf(NUMBER_DEFAULT, returnType);
                    }
                }
                if (returnType.equals(Void.class) || returnType.equals(void.class)) {
                    return null;
                }
                if ((returnType.getModifiers() & Modifier.ABSTRACT) != 0 && (returnType.getModifiers() & Modifier.INTERFACE) == 0) {
                    return null;
                }
                return validInstance(returnType);
            });
        }

        //最后检查缓存
        Object cache = mCacheObj.get(clazz);
        if (cache != null) {
            return (T) cache;
        }

        return null;
    }

    private static Object[] genericEmptyConstructorParams(Constructor<?> constructor) {
        Object[] params = new Object[constructor.getParameterCount()];

        Class<?>[] parameterTypes = constructor.getParameterTypes();

        for (int i = 0; i < parameterTypes.length; i++) {
            Class<?> parameterType = parameterTypes[i];
            if (isBasicType(parameterType)) {
                if (parameterType.getName().equals(JAVA_LANG_BOOLEAN)) {
                    params[i] = basicValueOf(BOOLEAN_DEFAULT, parameterType);
                } else {
                    params[i] = basicValueOf(NUMBER_DEFAULT, parameterType);
                }
            } else if (isArray(parameterType)) {
                params[i] = forArray(parameterType);
            } else {
                params[i] = validInstance(parameterType);
            }
        }
        return params;
    }


    private static Object forArray(Class<?> obj) {
        if (obj.equals(byte[].class)) {
            return new byte[]{};
        }

        if (obj.equals(short[].class)) {
            return new short[]{};
        }

        if (obj.equals(char[].class)) {
            return new char[]{};
        }

        if (obj.equals(int[].class)) {
            return new int[]{};
        }

        if (obj.equals(long[].class)) {
            return new long[]{};
        }

        if (obj.equals(float[].class)) {
            return new float[]{};
        }

        if (obj.equals(double[].class)) {
            return new double[]{};
        }

        if (obj.equals(boolean[].class)) {
            return new boolean[]{};
        }

        if (obj.equals(byte[][].class)) {
            return new byte[][]{};
        }

        if (obj.equals(short[][].class)) {
            return new short[][]{};
        }

        if (obj.equals(char[][].class)) {
            return new char[][]{};
        }

        if (obj.equals(int[][].class)) {
            return new int[][]{};
        }

        if (obj.equals(long[][].class)) {
            return new long[][]{};
        }

        if (obj.equals(float[][].class)) {
            return new float[][]{};
        }

        if (obj.equals(double[][].class)) {
            return new double[][]{};
        }

        if (obj.equals(boolean[][].class)) {
            return new boolean[][]{};
        }

        //for object, or int[][][][][].... and so on.
        return new Object[]{};
    }

    private static boolean isArray(Class<?> obj) {
        return (obj.equals(byte[].class)) ||
                (obj.equals(boolean[].class)) ||
                (obj.equals(short[].class)) ||
                (obj.equals(int[].class)) ||
                (obj.equals(long[].class)) ||
                (obj.equals(float[].class)) ||
                (obj.equals(double[].class)) ||
                (obj.equals(char[].class)) ||
                (obj.getName().startsWith("["));
    }

    private static boolean isBasicType(Type type) {
        if (type == null) {
            return false;
        }
        return sBasicClass.contains(type.getTypeName());
    }


    private static void ignore(Throwable e) {
        //ignore
    }


    private static Object basicValueOf(String value, Class<?> className) {
        return basicValueOf(value, className.getName());
    }

    private static Object basicValueOf(String value, @BasicType String className) {
        Object obj = null;

        switch (className) {
            case JAVA_INTEGER:
            case JAVA_LANG_INTEGER:
                obj = Integer.valueOf(value);
                break;
            case JAVA_LONG:
            case JAVA_LANG_LONG:
                obj = Long.valueOf(value);
                break;
            case JAVA_BYTE:
            case JAVA_LANG_BYTE:
                obj = Byte.valueOf(value);
                break;
            case JAVA_BOOLEAN:
            case JAVA_LANG_BOOLEAN:
                obj = Boolean.valueOf(value);
                break;
            case JAVA_SHORT:
            case JAVA_LANG_SHORT:
                obj = Short.valueOf(value);
                break;
            case JAVA_LANG_STRING:
                obj = value;
                break;
            case JAVA_CHARACTER:
            case JAVA_LANG_CHARACTER:
                obj = value.toCharArray()[0];
                break;
            case JAVA_DOUBLE:
            case JAVA_LANG_DOUBLE:
                obj = Double.valueOf(value);
                break;
            case JAVA_FLOAT:
            case JAVA_LANG_FLOAT:
                obj = Float.valueOf(value);
                break;
        }
        return obj;
    }

    /**
     * 反射方式验证observer
     * 为什么要返回呢？因为可能有些参数需要自定义，否则走不到那个分支
     */
    public static Object testObserver(@NonNull Object target, @NonNull String observerField) {
        Object obj = null;
        try {
            Field observer = target.getClass().getDeclaredField(observerField);
            observer.setAccessible(true);
            obj = observer.get(target);

            if (obj == null) {
                return null;
            }
            testObjectAllMethods(obj);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    public static void doUnitTest(Object target) {
        if (target == null) {
            return;
        }

        testObjectAllMethods(target);
        Field[] fields = target.getClass().getDeclaredFields();
        for (Field field : fields) {
            String name = field.getName().toLowerCase();
            if (name.contains("observer") || name.contains("call") || name.contains("listener")) {
                try {
                    field.setAccessible(true);
                    Object o = field.get(target);
                    if (o == null) {
                        continue;
                    }
                    testObjectAllMethods(o);
                } catch (IllegalAccessException ignore) {
                }
            }
        }
    }


    /**
     * 反射方式单元测试
     */
    public static void testObjectAllMethods(@NonNull Object target) {
        try {
            Method[] methods = target.getClass().getDeclaredMethods();
            //foreach methods
            for (Method method : methods) {
                try {
                    Class<?>[] parameterTypes = method.getParameterTypes();
                    //这里面保存了泛型字段
                    Parameter[] parameters = method.getParameters();
                    Object[] params = new Object[parameterTypes.length];
                    for (int i = 0; i < parameterTypes.length; i++) {
                        params[i] = validTestParameterInstance(parameters[i]);
                    }
                    //test method
                    method.setAccessible(true);
                    method.invoke(target, params);
                } catch (Exception ignored) {
                }
            }
        } catch (Exception ignored) {
        }
    }

    @SuppressWarnings("unchecked")
    private static Object validTestParameterInstance(Parameter parameter) {
        Object params = validInstance(parameter.getType());

        if (params instanceof ArrayList) {
            //对于list，往往需要有个数据
            Class<?> tClass = findTClass(parameter);
            if (tClass == null) {
                return params;
            }

            ((ArrayList) params).add(validInstance(tClass));
        }

        if (params instanceof HashSet) {
            //对于set，往往需要有个数据
            Class<?> tClass = findTClass(parameter);
            if (tClass == null) {
                return params;
            }

            ((HashSet) params).add(validInstance(tClass));
        }

        return params;
    }

    private static Class<?> findTClass(Parameter parameter) {
        String s = parameter.toString();

        if (!s.contains("<")) {
            return null;
        }

        int startIndex;
        String extents = "extends ";
        if (s.contains(extents)) {
            startIndex = s.indexOf(extents) + extents.length();
        } else {
            startIndex = s.indexOf("<") + 1;
        }

        int endIndex = s.indexOf(">");

        if (startIndex < 0 || endIndex < 0) {
            return null;
        }

        String tClass = s.substring(startIndex, endIndex);

        try {
            return Class.forName(tClass);
        } catch (ClassNotFoundException ignore) {
        }
        return null;
    }


    public static void setField(Object obj, String name, Object value) {
        if (obj == null) {
            return;
        }
        try {
            Field field = obj.getClass().getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
        }
    }

    @Retention(RetentionPolicy.SOURCE)
    @Target(value = ElementType.PARAMETER)
    @StringDef(
            value = {
                    JAVA_LANG_STRING,
                    JAVA_LANG_INTEGER,
                    JAVA_LANG_BYTE,
                    JAVA_LANG_SHORT,
                    JAVA_LANG_LONG,
                    JAVA_LANG_FLOAT,
                    JAVA_LANG_DOUBLE,
                    JAVA_LANG_BOOLEAN,
                    JAVA_LANG_CHARACTER
            }
    )
    private @interface BasicType {
    }


}
